import java.util.ArrayList;
import java.util.Map;

import com.sun.org.apache.xerces.internal.util.SymbolTable;

import Enumerators.TokenType;

/**
 * Classe que implementa o analisador Sintatico. Metodo de conversao direta
 * de gramaticas em automatos de pilha estruturados quase-otimos.
 * 
 * @param
 * 		token ...
 * @return
 * 		....
 */
public class SyntacticAnalyser {
	ArrayList<SubMachine> submachines;
	LexicalAnalyzer lex;
	SemanticAnalyser sem;
	
	public SyntacticAnalyser(LexicalAnalyzer lexicalAnalyzer,SemanticAnalyser sem){
		this.submachines = new ArrayList<SubMachine>();
		this.lex = lexicalAnalyzer;
		this.sem = sem;
	}
	public void createSubMachines(){

		SubMachine comando_estruturado = new SubMachine("comando_estruturado",this.lex, this.sem);
		
		//SUBMAQUINA EXPRESSOES
		SubMachine expressao = new SubMachine("expressao",this.lex, this.sem);
		this.submachines.add(expressao);
		expressao.addState(0);
		expressao.setFirstState(0);
		expressao.addState(2);
		expressao.addTransition(0, "(", 2);
		expressao.addState(3);
		expressao.addTransition(2, expressao, 3);
		expressao.addState(4);
		expressao.setFinalState(4);
		expressao.addTransition(0, "NUM", 4);
		expressao.addTransition(0, "ID", 4);
		expressao.addTransition(3, ")", 4);
		expressao.addTransition(4, "+", 0);
		expressao.addTransition(4, "*", 0);
		expressao.addTransition(4, "/", 0);
		expressao.addTransition(4, "-", 0);
				
		
		//SUBMAQUINA VARIAVEL
		SubMachine variavel = new SubMachine("variavel",this.lex, this.sem);
		this.submachines.add(variavel);
		variavel.addState(0);
		variavel.setFirstState(0);
		variavel.addState(1);
		variavel.setFinalState(1);
		variavel.addTransition(0, "ID", 1);
		variavel.addState(2);
		variavel.addTransition(1, "[", 2);
		variavel.addState(3);
		variavel.addTransition(2, expressao, 3);
		variavel.addTransition(3, "]", 1);
		
		
		//SUBMAQUINA BLOCO DE COMANDOS
		SubMachine bloco_comando = new SubMachine("bloco_comando",this.lex, this.sem);
		this.submachines.add(bloco_comando);
		//0
		bloco_comando.addState(0);
		bloco_comando.setFirstState(0);
		//1
		bloco_comando.addState(1);	
		//17
		bloco_comando.addState(17);
		bloco_comando.setFinalState(17);
		//2
		bloco_comando.addState(2);
		//3
		bloco_comando.addState(3);
		//4
		bloco_comando.addState(4);

		
		bloco_comando.addTransition(0, "COMECO", 1);	
		bloco_comando.addTransition(1, "FIM", 17);
		bloco_comando.addTransition(1, "CHAMAR", 4);		
		bloco_comando.addTransition(1, comando_estruturado, 1); 
		bloco_comando.addTransition(1, variavel, 2); //sempre colocar transicoes para submaquinas por ultimo
		bloco_comando.addTransition(2, ":=", 3);
		bloco_comando.addTransition(3, expressao, 1);
		bloco_comando.addTransition(3, variavel, 1);
		bloco_comando.addTransition(4, variavel, 1);
		
		
		//SUBMAQUINA COMANDO ESTRUTURADO
		//precisa ser revista (wirth) ***		
		this.submachines.add(comando_estruturado);
		//A
		comando_estruturado.addState(0);
		comando_estruturado.setFirstState(0);

		//B
		comando_estruturado.addState(1);
		//J
		comando_estruturado.addState(9);	
		//J
		comando_estruturado.addState(9);
		//K
		comando_estruturado.addState(10);
		//C
		comando_estruturado.addState(2);
		//D
		comando_estruturado.addState(3);
		//E
		comando_estruturado.addState(4);
		comando_estruturado.setFinalState(4);
		//I
		comando_estruturado.addState(8);
		//F
		comando_estruturado.addState(5);
		//L
		comando_estruturado.addState(11);
		
		//M
		comando_estruturado.addState(12);
		//G
		comando_estruturado.addState(6);
		//H
		comando_estruturado.addState(7);
		
		
		//transitions
		comando_estruturado.addTransition(0, "ENQUANTO", 1);
		comando_estruturado.addTransition(0, "SE", 5);		
		comando_estruturado.addTransition(1, expressao, 9);		
		comando_estruturado.addTransition(9, ">", 10);
		comando_estruturado.addTransition(9, "<", 10);
		comando_estruturado.addTransition(9, "==", 10);			
		comando_estruturado.addTransition(9, expressao, 10);		
		comando_estruturado.addTransition(10, expressao, 2);		
		comando_estruturado.addTransition(2, "FACA", 3);		
		comando_estruturado.addTransition(3, bloco_comando, 4);		
		comando_estruturado.addTransition(4, "SENAO", 8);		
		comando_estruturado.addTransition(8, bloco_comando, 4);		
		comando_estruturado.addTransition(5, expressao, 11);		
		comando_estruturado.addTransition(11, ">", 12);	
		comando_estruturado.addTransition(11, "<", 12);
		comando_estruturado.addTransition(11, "==", 12);		
		comando_estruturado.addTransition(12, expressao, 6);		
		comando_estruturado.addTransition(6, "ENTAO", 7);		
		comando_estruturado.addTransition(7, bloco_comando, 4);	
		
		
		//SUBMAQUINA MACRO-ESTRUTURA E FUNCOES
		SubMachine macro = new SubMachine("macro",this.lex, this.sem);
		this.submachines.add(macro);
		//0
		macro.addState(0);
		macro.setFirstState(0);
		//1
		macro.addState(1);
		//2
		macro.addState(2);
		//3
		macro.addState(3);
		//5
		macro.addState(5);
		//6
		macro.addState(6);
		//7
		macro.addState(7);
		//4
		macro.addState(4);
		macro.setFinalState(4);
				
		macro.addTransition(0, "PROGRAMA", 1);		
		macro.addTransition(1, "ID", 2);
		macro.addTransition(2, "VAR", 3);		
		macro.addTransition(2, bloco_comando, 4);		//sempre colocar transicoes para submaquinas por ultimo
		macro.addTransition(3, "ID", 5);
		macro.addTransition(5, ":", 6);
		macro.addTransition(6, "CHAR", 7);
		macro.addTransition(6, "INT", 7);
		macro.addTransition(6, "STRING", 7);		
		macro.addTransition(7, ";", 2);
		macro.addTransition(7, "[", 7);
		macro.addTransition(7, "]", 7);
		
		
		//SUBMAQUINA ROTINA
		SubMachine rotina = new SubMachine("rotina",this.lex, this.sem);
		this.submachines.add(rotina);
		//0
		rotina.addState(0);
		rotina.setFirstState(0);
		//1
		rotina.addState(1);
		//2
		rotina.addState(2);
		//4
		rotina.addState(4);
		rotina.setFinalState(4);
				
		rotina.addTransition(0, "ROTINA", 1);		
		rotina.addTransition(1, "ID", 2);	
		rotina.addTransition(2, bloco_comando, 4);		//sempre colocar transicoes para submaquinas por ultimo	
		rotina.addTransition(7, ";", 2);
		
				
		SubMachine smW = new SubMachine("escrita",this.lex, this.sem);
		this.submachines.add(smW);
		smW.addState(0);
		smW.setFirstState(0);
		smW.addState(1);
		smW.addTransition(0, "ESCREVER", 1);
		smW.addState(2);
		smW.addTransition(1, "STRING", 2);
		smW.addState(3);
		smW.setFinalState(3);
		smW.addTransition(2, ";", 3);
		
		SubMachine smR = new SubMachine("leitura",this.lex, this.sem);
		this.submachines.add(smR);
		smR.addState(0);
		smR.setFirstState(0);
		smR.addState(1);
		smR.addTransition(0, "LER", 1);
		smR.addState(2);
		smR.addTransition(1, "ID", 2);
		smR.addState(3);
		smR.setFinalState(3);
		smR.addTransition(2, ";", 3);
		
		// teste de chamada de sub-maquina
		SubMachine smRX = new SubMachine("leituraX",this.lex, this.sem);
		this.submachines.add(smRX);
		smRX.addState(0);
		smRX.setFirstState(0);
		smRX.addState(1);
		smRX.addTransition(0, "COMECO", 1);
		smRX.addState(2);
		smRX.addTransition(1, smR, 2);
		smRX.addState(3);
		smRX.addTransition(2, "FIM", 3);
		smRX.setFinalState(3);		
	}

	public SubMachine getSubMachine(String id){
		for(SubMachine sm: this.submachines){
			if(sm.getId()==id)
				return sm;
		}
		return null;
	}
	
	public static void main(String[] args){
		LexicalAnalyzer lex = new LexicalAnalyzer("syntacticAnalyserTest.petros");
		SemanticAnalyser semanticAnalyser = new SemanticAnalyser();
		semanticAnalyser.setSymbolTable(lex.getSymbolTable());
		SyntacticAnalyser syntactic = new SyntacticAnalyser(lex, semanticAnalyser);
		syntactic.createSubMachines();
		
		/*SubMachine test1 = syntactic.getSubMachine("escrita");
		test1.dump();
		test1.run();

		SubMachine test2 = syntactic.getSubMachine("leitura");
		test2.dump();
		test2.run();
		SubMachine test3 = syntactic.getSubMachine("leituraX");
		test3.dump();
		test3.run();
		
		SubMachine expressao = syntactic.getSubMachine("expressao");
		expressao.dump();
	    expressao.run();	
		
		SubMachine variavel = syntactic.getSubMachine("variavel");
		variavel.dump();
		variavel.run();
		*/
		SubMachine macro = syntactic.getSubMachine("macro");
		macro.dump();
		macro.run();
		semanticAnalyser.geraCodigo("outputPetros.asm");
		
		SubMachine rotina = syntactic.getSubMachine("rotina");
		Token token = lex.nextToken();
		while(lex.hasMoreTokens() && ! token.getValue().equals("FIM"))
		{
			lex.returnToken(token.getValue());
			rotina.dump();
			rotina.run();
			
			 token = lex.nextToken();
		}
		semanticAnalyser.geraCodigo("outputPetros.asm");
						
	}

	
	public class SubMachine{
		private String id;
		private int firstState, finalState;
		LexicalAnalyzer lexicalAnalyzer;
		ArrayList<Transition> trasitions;
		ArrayList<Integer> states;
		SemanticAnalyser sem;
		
		public SubMachine(String id, LexicalAnalyzer lexicalAnalyzer, SemanticAnalyser sem){
			this.id = id;
			this.lexicalAnalyzer = lexicalAnalyzer;
			this.trasitions = new ArrayList<Transition>();
			this.states = new ArrayList<Integer>();
			this.sem =sem;
		}
		
		public String getId() {
			return id;
		}

		public void addState(int state){
				this.states.add(state);
		}
		
		public void addTransition(int currentState, String tokenType, int nextState){
			// verifica se os estados ja exitem
			boolean current = false;
			boolean next = false;
			for(Integer state: this.states){
				if(state == currentState)
					current = true;
				if(state == nextState)
					next =true;		
			}
			if (current && next)
				this.trasitions.add(new Transition(currentState, tokenType,nextState));		
		}
		
		public void addTransition(int currentState, SubMachine subMachine, int nextState){
			// verifica se os estados ja exitem
			boolean current = false;
			boolean next = false;
			for(Integer state: this.states){
				if(state == currentState)
					current = true;
				if(state == nextState)
					next =true;
		
			}
			if (current && next)
				this.trasitions.add(new Transition(currentState, subMachine,nextState));		
		}
		
		
		public void setFinalState(int state){
			//verifica se o estado existe
			for(Integer st: this.states)
				if(st == state)
					this.finalState = state;
		}
		
		public int getFinalState(){
			return this.finalState;
		}
		
		public void setFirstState(int state){
			//verifica se o estado existe
			for(Integer st: this.states)
				if(st == state)
					this.firstState = state;
		}
		
		public int getFirstState(){
			return this.firstState;
		}
		
		public int getNextState(int state, Token token){
			String word= token.getType() == TokenType.KEYWORD? token.getValue().toString(): token.getType().toString();
			for (Transition tt: this.trasitions)
				if(tt.getCurrentState() == state){
					if(tt.getToken().equals(word)){
						return tt.getTargetState();
					}
					if(tt.getToken().equals("sm")){
						this.lexicalAnalyzer.returnToken(token.getValue());
						if(tt.callSubMachine())
							return tt.getTargetState();
					}
				}
			return -1;
		}
		
		public void dump(){
			System.out.println("SubMachine["+this.getId()+"]");
			System.out.print("States:");
			for(Integer st: this.states)			
				System.out.print(" "+st);
			
			System.out.println("\nFirst State: "+this.getFirstState()+"\nFinal State: "+this.getFinalState());
			System.out.println("Transitions:");
			for (Transition tt: this.trasitions)
				System.out.println("\t"+tt.toString());
			
		}
		
		public boolean run(){
			int st1 = this.getFirstState();
			int st2 = this.getFinalState();
			boolean subMachineEnd = false;
			Token token;			
			int possibleState = 0;
			
			//se nao existe a primeira transicao nessa maquina, devolver token e retornar false
			token = this.lexicalAnalyzer.nextToken();
			this.lexicalAnalyzer.returnToken(token.getValue());
			possibleState = this.getNextState(st1, token);
			if(possibleState == -1)
			{
				return false;
			}			
			
			System.out.println("run():");
			while (! subMachineEnd && this.lexicalAnalyzer.hasMoreTokens()){
				token = this.lexicalAnalyzer.nextToken();
				System.out.print("["+st1+","+token.getType().name()+ " : '" + token.getValue() + "'] =>");
				this.sem.semanticRoutine(this.getId(), st1, token);				
				st1 = this.getNextState(st1, token);
				if(st1 == st2)
				{
					possibleState = 0;
					if(this.lexicalAnalyzer.hasMoreTokens())
					{
						token = this.lexicalAnalyzer.nextToken();
						possibleState = this.getNextState(st1, token);
					}
					else
					{
						possibleState = -1;
					}
					
					
					if(possibleState == -1)
					{
						//call semantic for finalization purposes
						this.sem.semanticRoutine(this.getId(), st1, token);	
						subMachineEnd = true;
					}
					
					this.lexicalAnalyzer.returnToken(token.getValue());					
				}
				     
			}
			if(st1 == st2){
				System.out.println("> ACEITOU\n");
				return true;
			}else{
				System.out.println("> REJEITOU\n");
				return false;
			}
		}
		
		public class Transition{
			private int currentStateId;
			private String token;
			private SubMachine subMachine;
			private int targetStateId;
			
			public Transition(int currentStateId, String token, int targetStateId){
				this.currentStateId = currentStateId;
				this.token = token;
				this.targetStateId = targetStateId;

			}
			
			public Transition(int currentStateId, SubMachine submachine, int targetStateId){
				this.currentStateId = currentStateId;
				this.token = "sm";
				this.subMachine = submachine;
				this.targetStateId = targetStateId;
			}

			public int getCurrentState() {
				return currentStateId;
			}

			public void setCurrentState(int currentStateId) {
				this.currentStateId = currentStateId;
			}

			public String getToken() {
				return token;
			}

			public void setTokenType(String token) {
				this.token = token;
			}

			public int getTargetState() {
				return targetStateId;
			}
			
			public void setTargetState(int targetStateId) {
				this.targetStateId = targetStateId;
			}

			public boolean callSubMachine(){
				return this.subMachine.run();
			}
			
			public String getSubMachineId() {
				return this.subMachine.getId();
			}
			
			@Override
			public String toString(){
				return new String("Transition: ["+this.getCurrentState()+"]->["
						+(!this.getToken().equals("sm")? this.getToken(): this.getSubMachineId())
						+"]->["+this.getTargetState()+"]");
			}
			
		}
	}
}